<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>golang并发</title>
  
    <meta name="author" content="chimps">

    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--[if lt IE 9]>
      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->

    <!-- Le styles -->
    <link href="/assets/twitter/stylesheets/bootstrap.min.css" type="text/css" rel="stylesheet" media="all">
<link href="/assets/twitter/stylesheets/style.css" type="text/css" rel="stylesheet" media="all">
 
    <link href="/assets/twitter/widgets/google_prettify/stylesheets/twitter-bootstrap.css" type="text/css" rel="stylesheet" media="all">

    <!-- Le fav and touch icons -->
  <!-- Update these with your own images
    <link rel="shortcut icon" href="images/favicon.ico">
    <link rel="apple-touch-icon" href="images/apple-touch-icon.png">
    <link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png">
    <link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png">
  -->
  </head>

  <body>

    <div class="navbar">
      <div class="navbar-inner">
        <div class="container">
          <a class="brand" href="/">chimps</a>
          <ul class="nav">
            
              


  <li><a href="/archive">存档</a></li>


            
              


  <li><a href="/tags">标签</a></li>


            
              


  <li><a href="/categories">分类</a></li>


            
              


  <li><a href="/pages">页面</a></li>


            
              


  <li><a href="/about">关于我</a></li>


            
          </ul>
        </div>
      </div>
    </div>

    <div class="container">
      <div class="content">
        <div class="page-header">
  <h1>golang并发 </h1>
</div>

<div class="row">
  <div class="span12">
    <h3>Goroutine特点</h3>

<ul>
<li>Goroutine是Go并行设计的核心。goroutine说到底其实就是线程，但是它比线程更小，十几个goroutine可能体现在底层就是五六个线程，Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB)，当然会根据相应的数据伸缩。也正因为如此，可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。</li>
<li>Goroutine奉行通过通讯来共享内存，而不是共享内存来通讯</li>
</ul>

<h3>Goroutine - demo</h3>

<pre><code class="language-go">type c struct {
	word   string
	wordId int
	err    error
	place  int
}

func main() {
	cpunum := runtime.NumCPU()
	runtime.GOMAXPROCS(cpunum)
	ch := make(chan c, 100)
	t1 := time.Now().Unix()
	for i := 0; i &lt; 300; i++ {
		go test(i, ch)
	}
	for i := 0; i &lt; 300; i++ {
		select {
		case res := &lt;-ch:
			fmt.Println(res)
		}
	}
	t2 := time.Now().Unix()
	log.Println(t2, t1)
}

func test(i int, ch chan c) {
	time.Sleep(time.Second)
	res := c{&quot;test&quot;, i, nil, 0}
	ch &lt;- res
	return
}
</code></pre>

<h3>Channel操作符<code>&lt;-</code></h3>

<p>如果通过管道发送一个值，则将&lt;-作为二元操作符使用。通过管道接收一个值，则将其作为一元操作符使用:</p>

<pre><code class="language-go">ic &lt;- 3        // 往管道发送3
work := &lt;-wc    // 从管道接收一个指向Work类型值的指针
</code></pre>

<h3>Channel通道阻塞</h3>

<p>对于无缓冲的channel,<code>通道的发送/接收操作在对方准备好之前是阻塞</code></p>

<ul>
<li>对于同一个通道，发送操作（协程或者函数中的），在接收者准备好之前是阻塞的：如果ch中的数据无人接收，就无法再给通道传入其他数据：新的输入无法在通道非空的情况下传入。所以发送操作会等待 ch 再次变为可用状态：就是通道值被接收时（可以传入变量）。</li>
<li>对于同一个通道，接收操作是阻塞的（协程或函数中的），直到发送者可用：如果通道中没有数据，接收者就阻塞了。</li>
</ul>

<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    ch1 := make(chan int)
    go pump(ch1)       // pump hangs
    // 只是接受结果一次，也就是说，下面的send方再插入一个1，2就插入不进去了，阻塞了
    fmt.Println(&lt;-ch1) // prints only 0
}

func pump(ch chan int) {
    for i := 0; ; i++ {
        ch &lt;- i
    }
}
// 结果：0
</code></pre>

<h3>常见错误示例：</h3>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
)

func f1(in chan int) {
    fmt.Println(&lt;-in)
}

func main() {
    out := make(chan int)
    out &lt;- 2
    go f1(out)
}
</code></pre>

<p>上述例子会照成死锁，原因很简单，一般通讯的话，有两种情况</p>

<ul>
<li>没有缓冲时，需要最少<code>两个goroutine</code>准备好</li>
<li>有缓冲时，则是<code>goroutine - 中间缓冲层 - goroutine</code>之间的连接。</li>
</ul>

<h3>demo实例</h3>

<pre><code class="language-go">func main() {
	// 使用make创建一个channel
	channel := make(chan bool)
	go func() {
		fmt.Println(&quot;gogogo&quot;)
		// 向channel存数据
		channel &lt;- true
		// 关闭channel
		close(channel)
	}()
	// 迭代channel
	for v := range channel {
		fmt.Println(v)
	}
	fmt.Println(&quot;this is base&quot;)
}
// 结果
➜  first go run helloworld.go
gogogo
true
this is base
</code></pre>

<p>由结果可以看出：先迭代完成才会进行下一步操作， 也就是说，迭代的过程是阻塞的，如果channel中没有值，迭代就会一直等待，一直等到它有值，
如果channel没有关闭，则会发生死锁（deadlock）</p>

<h3>带缓冲的channel</h3>

<pre><code class="language-go">func main() {
	ch := make(chan string, 4)
	ch &lt;- &quot;zhangsan1&quot;
	ch &lt;- &quot;zhangsan2&quot;
	ch &lt;- &quot;zhangsan3&quot;
	ch &lt;- &quot;zhangsan4&quot;
	// 如果加上这一行，则会死锁，是因为当超过缓冲区的时候，就成阻塞模式了
	//ch &lt;- &quot;zhangsan5&quot;
	close(ch)
	fmt.Println(&lt;-ch)
	fmt.Println(&lt;-ch)
	fmt.Println(&lt;-ch)
	fmt.Println(&lt;-ch)
	fmt.Println(&lt;-ch)
}
// 结果
➜  first go run helloworld.go
zhangsan1
zhangsan2
zhangsan3
zhangsan4
         // 此处为string零值
</code></pre>

<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>

<h3>channel常与range配合</h3>

<pre><code class="language-go">func main() {
	ch := make(chan string)
	go generate(ch)
	for v := range ch {
		fmt.Printf(&quot;%s\n&quot;, v)
	}
}

func generate(ch chan string) {
	ch &lt;- &quot;zhangsan1&quot;
	ch &lt;- &quot;zhangsan2&quot;
	ch &lt;- &quot;zhangsan3&quot;
	ch &lt;- &quot;zhangsan4&quot;
	ch &lt;- &quot;zhangsan5&quot;
	close(ch)
}
</code></pre>

<h3>channel可用作信号源</h3>

<pre><code class="language-go">func main() {
	fmt.Println(&quot;Begin doing something!&quot;)
	c := make(chan bool)
	go func() {
		fmt.Println(&quot;Doing something…&quot;)
		close(c)
	}()
	&lt;-c
	fmt.Println(&quot;Done!&quot;)
}
</code></pre>

<h3>channel协同多个Goroutines</h3>

<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&rdquo;&lt;-start&rdquo;上，直到我们在main goroutine中给出开工的信号：&rdquo;close(start)&ldquo;，这些goroutines才开始真正的并发运行起来。</p>

<pre><code class="language-go">func worker(start chan bool, index int) {
        &lt;-start
        fmt.Println(&quot;This is Worker:&quot;, index)
}

func main() {
        start := make(chan bool)
        for i := 1; i &lt;= 100; i++ {
                go worker(start, i)
        }
        close(start)
        select {} //deadlock we expected
}
</code></pre>

<h3>channel唯一的ID服务</h3>

<pre><code class="language-go">package main

import &quot;fmt&quot;

func newUniqueIDService() &lt;-chan string {
    id := make(chan string)
    go func() {
        var counter int64 = 0
        for {
            // 无缓冲 -&gt; 阻塞
            id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)
            counter += 1
        }
    }()
    return id
}
func main() {
    id := newUniqueIDService()
    for i := 0; i &lt; 10; i++ {
        // 这边取一个，id插入一个（自增）
        fmt.Println(&lt;-id)
    }
}

$ go run testuniqueid.go
0
1
2
3
4
5
6
7
8
9
</code></pre>

<h3>惯用法：for/select</h3>

<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>

<pre><code class="language-go">for {
    select {
    case x := &lt;- somechan:
        // … 使用x进行一些操作

    case y, ok := &lt;- someOtherchan:
        // … 使用y进行一些操作，
        // 检查ok值判断someOtherchan是否已经关闭

    case outputChan &lt;- z:
        // … z值被成功发送到Channel上时

    default:
        // … 上面case均无法通信时，执行此分支
    }
}
</code></pre>

<h3>终结workers</h3>

<pre><code class="language-go">func main() {
	ch, die := make(chan bool), make(chan bool)
	for i := 0; i &lt; 10; i++ {
		go worker(die, i, ch)
		ch &lt;- true
	}
	time.Sleep(1 * time.Second)
	// 此处终结worker
	close(die)
}

func worker(die chan bool, index int, ch chan bool) {
	fmt.Println(&quot;Begin: This is Worker:&quot;, index)
	for {
		select {
		case &lt;-ch:
			fmt.Println(&quot;already worded:&quot;, index)
		case &lt;-die:
			return
		}
	}
}
</code></pre>

<h3>终结验证</h3>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    //&quot;time&quot;
)

func worker(die chan bool) {
    fmt.Println(&quot;Begin: This is Worker&quot;)
    for {
        select {
        //case xx：
        //做事的分支
        // get channel message
        case &lt;-die:
            fmt.Println(&quot;Done: This is Worker&quot;)
            // send channel message
            die &lt;- true
            return
        }
    }
}

func main() {
    die := make(chan bool)
    go worker(die)
    // send channel message
    die &lt;- true
    // get channel message
    &lt;-die
    fmt.Println(&quot;Worker goroutine has been terminated&quot;)
}
</code></pre>

<h3>等待goroutine完成</h3>

<p>使用<code>sync</code>包</p>

<pre><code class="language-go">func correct() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i &lt; 5; i++ {
        go func(n int) { // 使用局部变量
            fmt.Print(n)
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println()
}
</code></pre>

<p>上述只是等待goroutine完成，却没有进行通讯。有错误值也无法返回。所以说，最好还是使用channel
如果我们希望通讯，只需要在此基础上增加channel通讯即可
例：</p>

<pre><code class="language-go">func main() {
    people := []string{&quot;Anna&quot;, &quot;Bob&quot;, &quot;Cody&quot;, &quot;Dave&quot;, &quot;Eva&quot;}
    match := make(chan string, 1) // 为一个未匹配的发送操作提供空间
    wg := new(sync.WaitGroup)
    wg.Add(len(people))
    for _, name := range people {
        go Seek(name, match, wg)
    }
    wg.Wait()
    select {
    case name := &lt;-match:
        fmt.Printf(&quot;No one received %s’s message.\n&quot;, name)
    default:
        // 没有待处理的发送操作
    }
}

// 函数Seek 发送一个name到match管道或从match管道接收一个peer，结束时通知wait group
func Seek(name string, match chan string, wg *sync.WaitGroup) {
    select {
    case peer := &lt;-match:
        fmt.Printf(&quot;%s sent a message to %s.\n&quot;, peer, name)
    case match &lt;- name:
        // 等待某个goroutine接收我的消息
    }
    wg.Done()
}
</code></pre>

<h3>利用多核</h3>

<pre><code class="language-go">func init() {
    numcpu := runtime.NumCPU()
    runtime.GOMAXPROCS(numcpu) // 尝试使用所有可用的CPU
}
</code></pre>

    <hr>
    <div class="pagination">
      <ul>
        <ul>
          
            <li class="prev"><a href="/mongodb/mongodb%E5%AE%89%E8%A3%85%E8%BF%90%E8%A1%8C/" title="mongodb安装运行">&larr; Previous</a></li>
          
          

            <li><a href="/archive">Archive</a></li>

          
            <li class="next"><a href="/文摘/%E6%91%98%E5%BD%95/" title="摘录">Next &rarr;</a></li>
          
          
        </ul>
      </ul>
    </div>
    <hr>
    
  </div>
  
  <!-- <div class="span4">
    <h4>Published</h4>
    <div class="date"><span>2017-04-08</span></div>
    <br>
    <h4>Categories</h4>
    <ul class="tag_box">
    
      <li>
  <a href="/categories/#golang-ref">golang <span>3</span></a>
</li>
    
    </ul>
    <br>
    <h4>Tags</h4>
    <ul class="tag_box">
    
      <li>
  <a href="/tags/#golang-ref">golang <span>3</span></a>
</li>
    
      <li>
  <a href="/tags/#%E5%B9%B6%E5%8F%91-ref">并发 <span>3</span></a>
</li>
    
    </ul>
  </div> -->
</div>

      </div>

      <footer>
        <p>&copy; chimps 2013 
          with help from <a href="http://github.com/wendal/gor" target="_blank" title="Gor -- Fast Blog">Gor</a>
          and <a href="http://twitter.github.com/bootstrap/" target="_blank">Twitter Bootstrap</a>
		  and Idea from <a href="http://ruhoh.com" target="_blank" title="The Definitive Technical Blogging Framework">ruhoh</a>
        </p>
      </footer>

    </div> <!-- /container -->
    <script src="//cdnjscn.b0.upaiyun.com/libs/prettify/r298/prettify.min.js"></script>
    <script>
      var pres = document.getElementsByTagName("pre");
      for (var i=0; i < pres.length; ++i) {
        pres[i].className = "prettyprint linenums";
      }
      prettyPrint();
    </script>
    
    
  </body>
</html>
